package io.p2p.stream.p2pmain.handler;

import com.alibaba.fastjson.JSONObject;
import common.core.constans.NatType;
import common.core.domain.udp.UdpApiProtocol;
import common.core.util.RandomUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.socket.DatagramPacket;
import io.p2p.stream.p2pmain.model.MainSharedModel;
import io.p2p.stream.p2pmain.node.ActiveNode;
import io.p2p.stream.p2pmain.session.ActiveNodeSessionManager;
import lombok.extern.slf4j.Slf4j;

import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

@Slf4j
public class NatCheckHandler {

    private UdpApiProtocol apiProtocol;

    public NatCheckHandler(UdpApiProtocol apiProtocol){
        this.apiProtocol = apiProtocol;
    }

    public void natCheck() {
        String subType = apiProtocol.getBuffer().getString("subType");

        switch(subType){
            case "step1":
                step1();
                break;
            case "step2":
                step2();
                break;
            case "step3":
                step3();
                break;
            case "step4":
                step4();
                break;
            case "step5":
                step5();
                break;
            case "step6":
                step6();
                break;
            case "step7":
                step7();
                break;
        }
    }

    private void step1(){
        String name = apiProtocol.getBuffer().getString("name");

        ActiveNode activeNode = ActiveNodeSessionManager.getInstance().find(name);

        if(activeNode == null){
            return;
        }

        String localIP = apiProtocol.getBuffer().getString("localIP");
        int localPort = apiProtocol.getBuffer().getIntValue("localPort");

        if(!activeNode.getIp().equals(localIP) || activeNode.getPort() != localPort){

            ActiveNodeSessionManager.getInstance().flush(name, NatType.Symmetric);

            JSONObject natCheckAck = new JSONObject();
            natCheckAck.put("type","natCheckAck");
            natCheckAck.put("checked","true");
            natCheckAck.put("step","step1");
            natCheckAck.put("natType", NatType.Symmetric);
            //回一个数据包给Stun服务端
            apiProtocol.getCtx().writeAndFlush(new DatagramPacket(Unpooled.copiedBuffer(natCheckAck.toJSONString().getBytes()),apiProtocol.getRemoteAddress()));

            apiProtocol.getCtx().writeAndFlush(new DatagramPacket(Unpooled.copiedBuffer(natCheckAck.toJSONString().getBytes()),
                    new InetSocketAddress(activeNode.getIp(),activeNode.getPort())));
        } else{
            JSONObject natCheckAck = new JSONObject();
            natCheckAck.put("type","natCheckAck");
            natCheckAck.put("checked","false");
            natCheckAck.put("step","step2");
            natCheckAck.put("natType", NatType.Unknown);

            //回一个数据包给Stun服务端
            apiProtocol.getCtx().writeAndFlush(new DatagramPacket(Unpooled.copiedBuffer(natCheckAck.toJSONString().getBytes()),apiProtocol.getRemoteAddress()));

            //启用第二个IP和随机端口给客户端发送数据
            ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
            scheduler.scheduleAtFixedRate(()->{
                try{
                    int randomPort = RandomUtil.getRandomNumber(6000,6500);

                    DatagramSocket socket = new DatagramSocket(new InetSocketAddress(MainSharedModel.mainServerSecondIP,randomPort));
                    ByteBuf byteBuf = Unpooled.copiedBuffer(natCheckAck.toJSONString().getBytes());

                    byte[] buf = new byte[byteBuf.readableBytes()];
                    byteBuf.readBytes(buf);

                    java.net.DatagramPacket sendPacket = new java.net.DatagramPacket(buf,buf.length,
                            new InetSocketAddress(activeNode.getIp(), activeNode.getPort()));
                    socket.send(sendPacket);
                    socket.close();

                } catch (Exception e) {
                    log.error(e.getMessage());
                    return;
                }
                scheduler.shutdown();
            },0,200, TimeUnit.MILLISECONDS);

            natCheckAck.put("step","step3");
            apiProtocol.getCtx().writeAndFlush(new DatagramPacket(Unpooled.copiedBuffer(natCheckAck.toJSONString().getBytes()),
                    new InetSocketAddress(activeNode.getIp(),activeNode.getPort())));
        }
    }

    private void step2(){
        String name = apiProtocol.getBuffer().getString("name");

        ActiveNode activeNode = ActiveNodeSessionManager.getInstance().find(name);

        if(activeNode == null){
            return;
        }

        ActiveNodeSessionManager.getInstance().flush(name,NatType.FullCone);

        JSONObject natCheckAck = new JSONObject();
        natCheckAck.put("type","natCheckAck");
        natCheckAck.put("checked","true");
        natCheckAck.put("step","step2");
        natCheckAck.put("natType",NatType.FullCone);

        apiProtocol.getCtx().writeAndFlush(new DatagramPacket(Unpooled.copiedBuffer(natCheckAck.toJSONString().getBytes()),
                new InetSocketAddress(activeNode.getIp(),activeNode.getPort())));
    }

    private void step3(){
        String name = apiProtocol.getBuffer().getString("name");

        ActiveNode activeNode = ActiveNodeSessionManager.getInstance().find(name);

        if(activeNode == null){
            return;
        }

        JSONObject natCheckAck = new JSONObject();
        natCheckAck.put("type","natCheckAck");
        natCheckAck.put("checked","false");
        natCheckAck.put("step","step4");
        natCheckAck.put("natType", NatType.Unknown);

        //启用第二个IP和随机端口给客户端发送数据
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
        scheduler.scheduleAtFixedRate(()->{
            try{
                int randomPort = RandomUtil.getRandomNumber(6000,6500);

                DatagramSocket socket = new DatagramSocket(new InetSocketAddress(MainSharedModel.mainServerFirstIP,randomPort));
                ByteBuf byteBuf = Unpooled.copiedBuffer(natCheckAck.toJSONString().getBytes());

                byte[] buf = new byte[byteBuf.readableBytes()];
                byteBuf.readBytes(buf);

                java.net.DatagramPacket sendPacket = new java.net.DatagramPacket(buf,buf.length,
                        new InetSocketAddress(activeNode.getIp(), activeNode.getPort()));
                socket.send(sendPacket);
                socket.close();

            } catch (Exception e) {
                log.error(e.getMessage());
                return;
            }
            scheduler.shutdown();
        },0,200, TimeUnit.MILLISECONDS);

        natCheckAck.put("step","step5");
        apiProtocol.getCtx().writeAndFlush(new DatagramPacket(Unpooled.copiedBuffer(natCheckAck.toJSONString().getBytes()),
                new InetSocketAddress(activeNode.getIp(),activeNode.getPort())));
    }

    private void step4(){
        String name = apiProtocol.getBuffer().getString("name");

        ActiveNode activeNode = ActiveNodeSessionManager.getInstance().find(name);

        if(activeNode == null){
            return;
        }

        ActiveNodeSessionManager.getInstance().flush(name,NatType.FullCone);

        JSONObject natCheckAck = new JSONObject();
        natCheckAck.put("type","natCheckAck");
        natCheckAck.put("checked","true");
        natCheckAck.put("step","step4");
        natCheckAck.put("natType",NatType.RestrictedCone);

        apiProtocol.getCtx().writeAndFlush(new DatagramPacket(Unpooled.copiedBuffer(natCheckAck.toJSONString().getBytes()),
                new InetSocketAddress(activeNode.getIp(),activeNode.getPort())));
    }

    private void step5(){
        String name = apiProtocol.getBuffer().getString("name");

        ActiveNode activeNode = ActiveNodeSessionManager.getInstance().find(name);

        if(activeNode == null){
            return;
        }

        try{
            JSONObject natCheckAck = new JSONObject();
            natCheckAck.put("type","natCheckAck");
            natCheckAck.put("checked","false");
            natCheckAck.put("step","step6");
            natCheckAck.put("natType",NatType.PortRestrictedCone);

            DatagramSocket socket = new DatagramSocket(new InetSocketAddress(MainSharedModel.mainServerSecondIP,MainSharedModel.mainServerSecondPort));
            ByteBuf byteBuf = Unpooled.copiedBuffer(natCheckAck.toJSONString().getBytes());

            byte[] buf = new byte[byteBuf.readableBytes()];
            byteBuf.readBytes(buf);

            java.net.DatagramPacket sendPacket = new java.net.DatagramPacket(buf,buf.length,
                    new InetSocketAddress(activeNode.getIp(), activeNode.getPort()));
            socket.send(sendPacket);
            socket.close();

        } catch (Exception e) {
            log.error(e.getMessage());
        }

        JSONObject natCheckAck = new JSONObject();
        natCheckAck.put("type","natCheckAck");
        natCheckAck.put("checked","false");
        natCheckAck.put("step","step7");
        natCheckAck.put("natType",NatType.PortRestrictedCone);

        apiProtocol.getCtx().writeAndFlush(new DatagramPacket(Unpooled.copiedBuffer(natCheckAck.toJSONString().getBytes()),
                new InetSocketAddress(activeNode.getIp(),activeNode.getPort())));

    }

    private void step6(){
        String name = apiProtocol.getBuffer().getString("name");

        ActiveNode activeNode = ActiveNodeSessionManager.getInstance().find(name);

        if(activeNode == null){
            return;
        }

        ActiveNodeSessionManager.getInstance().flush(name,NatType.PortRestrictedCone);

        JSONObject natCheckAck = new JSONObject();
        natCheckAck.put("type","natCheckAck");
        natCheckAck.put("checked","true");
        natCheckAck.put("step","step6");
        natCheckAck.put("natType",NatType.PortRestrictedCone);

        apiProtocol.getCtx().writeAndFlush(new DatagramPacket(Unpooled.copiedBuffer(natCheckAck.toJSONString().getBytes()),
                new InetSocketAddress(activeNode.getIp(),activeNode.getPort())));
    }

    private void step7(){
        String name = apiProtocol.getBuffer().getString("name");

        ActiveNode activeNode = ActiveNodeSessionManager.getInstance().find(name);

        if(activeNode == null){
            return;
        }

        ActiveNodeSessionManager.getInstance().flush(name,NatType.Unknown);

        JSONObject natCheckAck = new JSONObject();
        natCheckAck.put("type","natCheckAck");
        natCheckAck.put("checked","false");
        natCheckAck.put("step","step8");
        natCheckAck.put("natType",NatType.Unknown);

        apiProtocol.getCtx().writeAndFlush(new DatagramPacket(Unpooled.copiedBuffer(natCheckAck.toJSONString().getBytes()),
                new InetSocketAddress(activeNode.getIp(),activeNode.getPort())));
    }
}
